﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Transactions;
using System.Xml;
using System.Xml.Linq;
using AuthoringClientWordAddIn.Services;
using Microsoft.Office.Core;
using Microsoft.Office.Interop.Word;
using Microsoft.Office.Tools.Ribbon;
using CustomTaskPane = Microsoft.Office.Tools.CustomTaskPane;

namespace AuthoringClientWordAddIn
{
    public partial class ThisAddIn
    {
        private CustomTaskPane _pane;

        private void ThisAddIn_Startup(object sender, EventArgs e)
        {
            Dictionary<string, string> documentProperties = GetDocumentProperties();
            var control = new PublishingPaneControl(this, documentProperties);
            _pane = CustomTaskPanes.Add(control, "Publishing");
            ShowPublishPane();
        }

        public void ShowPublishPane()
        {
            _pane.Visible = true;
        }

        private void ThisAddIn_Shutdown(object sender, EventArgs e)
        {
        }

        protected override IRibbonExtension[] CreateRibbonObjects()
        {
            return new[] { new PublishingRibbon(this) };
        }

        public Dictionary<string, string> GetDocumentProperties()
        {
            var properties = new Dictionary<string, string>();
            Document activeDocument = Application.ActiveDocument;
            dynamic builtInDocumentProperties = activeDocument.BuiltInDocumentProperties;
            dynamic customDocumentProperties = activeDocument.CustomDocumentProperties;
            AddDocumentProperties(properties, builtInDocumentProperties, "Title", "Subject", "Author");
            AddDocumentProperties(properties, customDocumentProperties);
            return properties;
        }

        private static void AddDocumentProperties(
            Dictionary<string, string> properties, DocumentProperties documentProperties, params string[] goodProperties)
        {
            foreach (DocumentProperty property in documentProperties)
            {
                string name = property.Name;
                dynamic value;
                if (goodProperties.Length != 0 && !goodProperties.Contains(name)) continue;
                if (property.Value == null)
                {
                    value = "";
                }
                else
                {
                    value = property.Value.ToString();
                }
                properties.Add(name, value);
            }
        }

        public void SaveDocumentProperties(Dictionary<string, string> documentProperties)
        {
            Document activeDocument = Application.ActiveDocument;
            DocumentProperties builtInDocumentProperties = activeDocument.BuiltInDocumentProperties;
            foreach (var documentProperty in documentProperties)
            {
                builtInDocumentProperties[documentProperty.Key].Value = documentProperty.Value;
            }
            activeDocument.Save();
        }

        public void Publish()
        {
            IPublishingManager pubService = new PublishingManagerClient();
            var incomingData = new IncomingDocumentData { ContentType = "Document", DocumentBytes = GetDocumentBytes() };

            using (var scope = new TransactionScope())
            {
                pubService.Publish(incomingData);
                scope.Complete();
            }
        }

        private byte[] GetDocumentBytes()
        {
            var document = Application.ActiveDocument;
            string wordOpenXml = document.WordOpenXML;
            var openDocument = XDocument.Load(new StringReader(wordOpenXml));
            var stream = new MemoryStream();
            FlatOpenXmlToOpc(openDocument, stream);
            return stream.ToArray();
        }

        // code from http://blogs.msdn.com/b/mcsuksoldev/archive/2010/04/09/sending-an-email-from-within-microsoft-word-with-the-word-document-attached.aspx
        public static void FlatOpenXmlToOpc(XDocument document, Stream docxStream)
        {
            XNamespace pkg = "http://schemas.microsoft.com/office/2006/xmlPackage";
            XNamespace rel = "http://schemas.openxmlformats.org/package/2006/relationships";

            using (Package package = Package.Open(docxStream, FileMode.Create))
            {
                // add all parts (but not relationships)
                foreach (XElement xmlPart in document.Root
                    .Elements()
                    .Where(
                        p =>
                        (string)p.Attribute(pkg + "contentType") !=
                        "application/vnd.openxmlformats-package.relationships+xml"))
                {
                    var name = (string)xmlPart.Attribute(pkg + "name");
                    var contentType = (string)xmlPart.Attribute(pkg + "contentType");
                    if (contentType.EndsWith("xml", StringComparison.OrdinalIgnoreCase))
                    {
                        var u = new Uri(name, UriKind.Relative);
                        PackagePart part = package.CreatePart(u, contentType, CompressionOption.SuperFast);
                        using (Stream str = part.GetStream(FileMode.Create))
                        using (XmlWriter xmlWriter = XmlWriter.Create(str))
                        {
                            xmlPart.Element(pkg + "xmlData")
                                .Elements()
                                .First()
                                .WriteTo(xmlWriter);
                        }
                    }
                    else
                    {
                        var u = new Uri(name, UriKind.Relative);
                        PackagePart part = package.CreatePart(u, contentType, CompressionOption.SuperFast);
                        using (Stream str = part.GetStream(FileMode.Create))
                        using (var binaryWriter = new BinaryWriter(str))
                        {
                            var base64StringInChunks = (string)xmlPart.Element(pkg + "binaryData");
                            char[] base64CharArray = base64StringInChunks.Where(c => c != '\r' && c != '\n').ToArray();
                            byte[] byteArray = Convert.FromBase64CharArray(base64CharArray, 0, base64CharArray.Length);
                            binaryWriter.Write(byteArray);
                        }
                    }
                }

                foreach (XElement xmlPart in document.Root.Elements())
                {
                    var name = (string)xmlPart.Attribute(pkg + "name");
                    var contentType = (string)xmlPart.Attribute(pkg + "contentType");
                    if (contentType == "application/vnd.openxmlformats-package.relationships+xml")
                    {
                        if (name == "/_rels/.rels")
                        {
                            // add the package level relationships
                            foreach (XElement xmlRel in xmlPart.Descendants(rel + "Relationship"))
                            {
                                var id = (string)xmlRel.Attribute("Id");
                                var type = (string)xmlRel.Attribute("Type");
                                var target = (string)xmlRel.Attribute("Target");
                                var targetMode = (string)xmlRel.Attribute("TargetMode");
                                if (targetMode == "External")
                                {
                                    package.CreateRelationship(
                                        new Uri(target, UriKind.Absolute), TargetMode.External, type, id);
                                }
                                else
                                {
                                    package.CreateRelationship(
                                        new Uri(target, UriKind.Relative), TargetMode.Internal, type, id);
                                }
                            }
                        }
                        else
                        {
                            // add part level relationships
                            string directory = name.Substring(
                                0, name.IndexOf("/_rels", StringComparison.OrdinalIgnoreCase));
                            string relsFilename = name.Substring(name.LastIndexOf('/'));
                            string filename = relsFilename.Substring(
                                0, relsFilename.IndexOf(".rels", StringComparison.OrdinalIgnoreCase));
                            PackagePart fromPart = package.GetPart(new Uri(directory + filename, UriKind.Relative));
                            foreach (XElement xmlRel in xmlPart.Descendants(rel + "Relationship"))
                            {
                                var id = (string)xmlRel.Attribute("Id");
                                var type = (string)xmlRel.Attribute("Type");
                                var target = (string)xmlRel.Attribute("Target");
                                var targetMode = (string)xmlRel.Attribute("TargetMode");
                                if (targetMode == "External")
                                {
                                    fromPart.CreateRelationship(
                                        new Uri(target, UriKind.Absolute), TargetMode.External, type, id);
                                }
                                else
                                {
                                    fromPart.CreateRelationship(
                                        new Uri(target, UriKind.Relative), TargetMode.Internal, type, id);
                                }
                            }
                        }
                    }
                }
            }
        }

        #region VSTO generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            Startup += ThisAddIn_Startup;
            Shutdown += ThisAddIn_Shutdown;
        }

        #endregion
    }
}